package com.sky.controller.admin;

import com.sky.constant.JwtClaimsConstant;
import com.sky.dto.EmployeeDTO;
import com.sky.dto.EmployeeLoginDTO;
import com.sky.dto.EmployeePageQueryDTO;
import com.sky.entity.Employee;
import com.sky.mapper.EmployeeMapper;
import com.sky.properties.JwtProperties;
import com.sky.result.PageResult;
import com.sky.result.Result;
import com.sky.service.EmployeeService;
import com.sky.utils.JwtUtil;
import com.sky.vo.EmployeeLoginVO;
import io.swagger.annotations.Api;
import io.swagger.annotations.ApiOperation;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.*;

import java.util.HashMap;
import java.util.Map;

//controller是控制层

/**
 * 员工管理
 * 这是一整个大的模块,对应前端页面中的"员工管理",可以打开前端看看
 */
@RestController
//这个注解是一个组合注解:注入到容器中+返回json格式数据
//是 @Controller 和 @ResponseBody 这两个注解的组合体。
//因为目前的项目是前后端分离的,所以一般这个数据都是返回json格式的数据;而不是请求转发到别的前端页面
@RequestMapping("/admin/employee")
/**
 * 1.上面是映射的路径,当前端访问这个http://localhost/admin/employee,
 *   对应后端的地址应该是:  http://localhost:8080/admin/employee   就会发送请求到后端当前的controller;
 *   因为后端是部署在tomcat服务器上的,那也就是说明,这两个请求的地址并不一样,,那就不应该请求成功
 *   然后下面代码中,比如@PostMapping("/login") ,就是应该访问http://localhost:8080/admin/employee/login,
 *     但是你发现前端页面登录成功了,也就是说请求成功了,,
 *     请求的路径不同(端口号都不同)为啥会成功呢,这就涉及到 "苍穹外卖day01_3.2.5_nginx反向代理和负载均衡"笔记
 * 也就是说前端的请求,并不是直接请求到后端的服务器的,而是通过nginx进行了转发
 * 2.上面没有明确指定端口号,用的是http协议,那默认是80端口;
 * 对比这的区个默认端口和tomcat端口8080,以及tomcat在当前项目中起到了什么作用,看Java笔记_yue.md,第57点
 */
@Slf4j  //用日志的方式在控制台输出,,就52行的log.info
//@Slf4j是属于lombok的,@Slf4j注解为当前类提供了一个名为log的日志对象，可以在类中直接使用log对象来记录日志
@Api(tags="员工相关接口")
//这个@Api是swagger的注解,用来生成api接口文档
    //tags="员工相关接口"就是对当前EmployeeController这个类,生成api文档做的一个说明
public class EmployeeController {

    @Autowired   //自动装配
    private EmployeeService employeeService;
    @Autowired
    private JwtProperties jwtProperties;
    //这是一个用来管理配置属性的类,在sky-common目录对应的properties目录下;
    //配置属性类;用来封装springboot的配置文件,,对应的是.yml配置文件中中的属性;可以去类里面看具体的定义


    /**
     * 员工登录,生成jwt令牌,对明文密码进行MD5加密
     * 在EmployeeServiceImpl.java代码中,对明文密码进行MD5加密的
     * 用EmployeeLoginDTO这个DTO类,来接收对应的前端登录信息(员工登录时传递的数据格式)
     * 用EmployeeLoginVO这个VO类,将返回的结果进行包装:(员工登录返回的数据格式)
     * 最终又封装成Result对象,进行处理,然后返回给前端
     * @param employeeLoginDTO
     * @return
     */
    @PostMapping("/login")   //controller层就是用来关联前端的路径的;这里就是用来处理前端发来的post请求
    /**
     * 都属于Rest风格的请求,get查询,post添加,delete删除,put修改
     */
    @ApiOperation(value="员工登录")
    //@ApiOperation这个注解也是swagger的注解,作用于方法上,,后面的value="员工登录"来描述当前的方法login的作用
    public Result<EmployeeLoginVO> login(@RequestBody EmployeeLoginDTO employeeLoginDTO) {
        //上面这里接收参数用的是DTO,可以看EmployeeLoginDTO这个类中的解释
        //@RequestBody是springboot中接收参数的相关注解,P232;用来接收客户端发送的 JSON 格式数据

        log.info("员工登录：{}", employeeLoginDTO);
        //日志输出,输出到控制台中,下面是输出的内容
        //员工登录：EmployeeLoginDTO(username=admin, password=123456)


        Employee employee = employeeService.login(employeeLoginDTO);
        //通过调用login方法,返回对应的employee对象
            //里面会通过获取到的用户名和密码进行比对,通过对应的mapper运行sql语句,根据用户名查询数据库中的数据

        //登录成功后，生成jwt令牌(这个不理解,就只能多看,照猫画虎,学着用)
        Map<String, Object> claims = new HashMap<>();
        claims.put(JwtClaimsConstant.EMP_ID, employee.getId());
        //JwtClaimsConstant这个类在sky-common对应的constant常量的目录下,
            //JwtClaimsConstant.EMP_ID就是对应一个常量值,"empId"作为键值对中的key,因为要放入到Map集合中
        //employee.getId()表示传入对应的id
        String token = JwtUtil.createJWT(
                jwtProperties.getAdminSecretKey(),
                jwtProperties.getAdminTtl(),
                claims);
        //上面的JwtUtil在sky-common对应的utils工具的目录下,,就是定义的一个工具类,专门用来生成jwt令牌
        //jwtProperties这是一个配置属性类(在sky-common对应的properties目录下);
            // 用来封装springboot的配置文件,就是对应的.yml配置文件,具体的方法可以跳进去查看
            //getAdminSecretKey和getAdminTtl就是获取对应的"管理端员工"生成jwt令牌的相关配置

        //把前端登录的数据再进行封装一下,封装成对象(员工登录返回的数据格式)
            //下面的builder是构建器进行封装;以前都是通过new一个对象,然后通过set方法进行封装的对象
            //通过builder然后调用自带的方法,就可以直接实现对象的封装;
                // 这里其实对应的.id是builder的方法,只是和对应的属性名一致;
                // 需要注意,在EmployeeLoginVO这个类上,必须要加上对应的@Builder注解,才能用来直接封装对象
        EmployeeLoginVO employeeLoginVO = EmployeeLoginVO.builder()
                .id(employee.getId())
                .userName(employee.getUsername())
                .name(employee.getName())
                .token(token)
                .build();
       //1.在 EmployeeLoginVO 类上使用 @Builder 注解时，Lombok 会在编译时自动生成一个静态内部类 Builder，
       //这个类包含了设置 EmployeeLoginVO 实例属性的方法，并且这些方法返回 Builder 类的实例本身，
       //以支持链式调用。最后，Builder 类还包含一个 build() 方法，用于创建并返回 EmployeeLoginVO 的实例。
       //2.这里的 .id(), .userName(), .name(), 和 .token() 方法是 Lombok 自动生成的 Builder 类中的方法，
       //它们用于设置 EmployeeLoginVO 实例的属性。.build() 方法也是自动生成的，用于创建并返回 EmployeeLoginVO 的实例。


        return Result.success(employeeLoginVO);//把对象传入,成功返回
        //这里相当于把我们的后端结果再进行封装,封装到Result对象中;  然后整体返回给前端进行使用,就可以了
        //Result这个类在sky-common对应的Result这个目录下
    }

    /**
     * 退出
     *
     * @return
     */
    @PostMapping("/logout")
    @ApiOperation("员工退出")   //这个也是swagger的注解,作用在方法上,描述为"员工退出"
    public Result<String> logout() {
        return Result.success();
    }

    /**
     * 新增员工的功能
     * @param employeeDTO
     * @return
     */
    //可以看到接口设计的请求方式是post,所以这里要用post请求;这里的路径在上面就设置了一个作用于全类的路径,32行
    @PostMapping
    @ApiOperation("新增员工")   //用来生成接口文档
    public Result save(@RequestBody EmployeeDTO employeeDTO){
        //用employeeDTO来接收前端发来的参数;添加@RequestBody注解,因为是以json格式接收的数据

        //方便调试,用log.info输出一下
        log.info("新增员工:{}",employeeDTO);   //{}这是一个占位符,就把后面的这个参数动态的填充到{}对应的位置
        //打印出当前线程的id
        System.out.println("当前线程的id:" + Thread.currentThread().getId());
        //Thread.currentThread().getId()通过这个Thread的方法就能获取到当前线程的id

        //具体实现新增的功能,调用service层对应的save方法
        employeeService.save(employeeDTO);
        return Result.success();
    }

    /**
     * 员工分页查询
     * @param employeePageQueryDTO
     * @return
     */
    @GetMapping("/page")    //对应的请求方式,和请求路径
    @ApiOperation("员工分页查询")   //生成对应的API文档
    public Result<PageResult> page(EmployeePageQueryDTO employeePageQueryDTO){
        //接收参数,用的EmployeePageQueryDTO;为啥没像前面那样直接用EmployeeDTO
            //可以看下接口文档(可以直接看对应笔记--day02_员工分页查询_接口设计),请求参数是Query类型,并不是json格式的
            //1.所以上面参数没有添加@RequestBody的注解;;并且这种Query类型,本质是通过地址栏?的方式直接携带参数
                //Query忘了不懂也没关系,只要记得就是url后面用?携带;  而路径参数就是url后面用/{}来携带,两种方式而已
            //2.并且这里用于分页查询,请求参数也不一样,所以单独封装了一个分页查询的DTO,即EmployeePageQueryDTO

        //便于控制台的调试,输出一下
        log.info("员工分页查询,参数为:{}",employeePageQueryDTO);
        //调用service层,实现真正的查询代码
        PageResult pageResult = employeeService.pageQuery(employeePageQueryDTO);
        return Result.success(pageResult);
    }

    /**
     * 启用/禁用员工账号
     * @param status
     * @param id
     * @return
     */
    @PostMapping("status/{status}")     //post请求:表示保存,新建;   put请求才是修改
    @ApiOperation("启用禁用员工账号")      //用来生成API文档
    public Result startOrStop(@PathVariable Integer status,Long id){
        //1.老师说,一般涉及到查询,这里Result对应的泛型再写上;因为你查询要返回Result里面对应的data数据,
            // 而对于不是查询的操作,那只需要返回对应的Result里面的状态码就可以,所以可以不写泛型
            //综上所述,老师这里的Result就没加泛型
        //2.这里的状态码比较简单,所以老师直接在请求后面携带的参数,,,直接携带,就是在请求路径后面直接携带,最初始讲过
            // 所以这里接收前端发来的参数,就不用前面的方法,封装成对应的DTO,如employeeDTO才来接收;
            //而是直接以最简单的方式接收,看API文档,请求参数有两个,一个是status(Interger类型),一个是id(用的Long类型)
            //补充:为啥这里的id用的long型,因为实体类中id定义的时候就是long型,其实感觉也不是强制的
        //3.@PathVariable表示这是一个路径参数,,,因为你这个参数,本来在接口设计中就是直接放到路径上来传递的,
            //  Path: /admin/employee/status/{status}  这里后面的{status} 就是携带的参数,所以这里是路径参数
            //上面会将接受到的status参数,传递给当前方法形参中的status
            //补充说明:后面传入的形参因为命名一致也是status,所以没加别名status,要保证能匹配得上;
                // 否则完整的应该是 (@PathVariable(value="status") Integer status,Long id)
        //4.为啥后面的id,不用加上路径参数对应的注解@PathVariable;
            //我明白为啥了,,看上面"员工分页查询"对应的page方法传入的参数也没有添加注解,
                // 因为它请求的是请求参数是Query类型;所以可以直接请求;
            // 而当前的status是路径参数所以要加@PathVariable;
            // 而"员工登陆"对应的login方法请求参数类型是json格式,所以要加上@RequestBody注解

        //输出一下日志,用来检测当前方法是否执行;并用两个占位符{},输出了对应的status和id
        log.info("启用禁用员工账号:{},{}",status,id);

        employeeService.startOrStop(status,id);
        return Result.success();
        //这里Result就可以只调用无参的这个方法,因为只传状态码,不用data数据,所以也是为啥上面不用加泛型的原因
    }

    /**
     * 根据id来查询员工信息:用于对话框的数据回显
     * @param id
     * @return
     */
    @GetMapping("/{id}")   //这说明这里请求参数类型是路径参数
    @ApiOperation("根据id来查询员工信息")      //用来生成对应的API文档
    public Result<Employee> getById(@PathVariable Long id){
        //上面设计过员工分页查询了,那为啥这里还要在设计一遍查询员工信息
            //1.这个查询用来数据回显,就是点击编辑的按钮会出现弹窗,然后它查到的数据,自动填充
        Employee employee = employeeService.getById(id);
        //希望返回值是Employee对象,所以这里用Employee接收

        return Result.success(employee);
    }

    /**
     * 编辑员工信息
     * @param employeeDTO
     * @return
     */
    @PutMapping    //这里的是修改操作,所以用的put请求;
    @ApiOperation("编辑员工信息")
    //因为这个并不是查询操作,不返回date数据,所以不用写泛型
    public Result update(@RequestBody EmployeeDTO employeeDTO){
        //接受json格式数据,所以这里要加上@RequestBody注解

        //log输出,便于测试
        log.info("编辑员工信息: {}",employeeDTO);
        employeeService.update(employeeDTO);
        return Result.success();
    }
}
